package com.bridgeintelligent.tag.engine.mvc.service.impl;

import com.bridgeintelligent.tag.engine.api.DimInfo;
import com.bridgeintelligent.tag.engine.api.DimItemInfo;
import com.bridgeintelligent.tag.engine.api.EngineException;
import com.bridgeintelligent.tag.engine.api.IndexNameInfo;
import com.bridgeintelligent.tag.engine.api.rsp.DimAggregationRsp;
import com.bridgeintelligent.tag.engine.core.cache.CacheConfig;
import com.bridgeintelligent.tag.engine.core.cache.CacheUtils;
import com.bridgeintelligent.tag.engine.es.base.ESQueryUtils;
import com.bridgeintelligent.tag.engine.es.model.DimAggregationModel;
import com.bridgeintelligent.tag.engine.es.model.GroupItem;
import com.bridgeintelligent.tag.engine.es.query.EsQueryBuilder;
import com.bridgeintelligent.tag.engine.mvc.service.IDimAggregationService;
import com.bridgeintelligent.tag.engine.mvc.service.IEsDateService;
import com.bridgeintelligent.tag.engine.parse.BaseParse;
import com.bridgeintelligent.tag.utils.FieldHandler;
import com.wayneleo.quickstart.framework.ExceptionCode;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.BucketOrder;
import org.elasticsearch.search.aggregations.bucket.range.DateRangeAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.range.Range;
import org.elasticsearch.search.aggregations.bucket.range.RangeAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.Avg;
import org.elasticsearch.search.aggregations.metrics.Max;
import org.elasticsearch.search.aggregations.metrics.Min;
import org.elasticsearch.search.aggregations.metrics.Sum;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * xiangjialiang
 */
@Service
@Slf4j
@AllArgsConstructor
public class DimAggreagtionServiceImpl implements IDimAggregationService {

    final private IEsDateService dateService;

    private static final String CUST_NUM = "cust_num";
    /**
     * 校验数字类型
     */
    final private Pattern pattern = Pattern.compile("^(\\-|\\+)?\\d+(\\.\\d+)?$");

    @Override
    public DimAggregationRsp getFieldAggregation(DimAggregationModel model) throws IOException {
        //获取标签基本信息
        DimInfo cacheInfo = CacheUtils.getCacheInfo(CacheConfig.DIM_INFO, model.getField());
        if(cacheInfo==null){
            throw new EngineException(new ExceptionCode(999,"未查询到标签"));
        }
        if(StringUtils.equals(cacheInfo.getClassify(),"04")){
            throw new EngineException(new ExceptionCode(999,"标识标签不支持"));
        }
        Map<String, String> relation = dateService.symbolRelation(model.getIndexId());
        //根据动态列的时间后缀
        if (!CollectionUtils.isEmpty(model.getFields())){
            Map map = new HashMap();
            model.getFields().forEach(e->{
                map.put(e, FieldHandler.addSuffixField(e,model.getDataDate(),relation));
            });
            model.setFieldsMap(map);
        }
        DimAggregationRsp rsp;
        // 如果是数值类型标签
        if(StringUtils.equals(cacheInfo.getClassify(),"03")){
            rsp = getNumberAggregationValue(model, cacheInfo,relation);
            //如果是日期类型标签
        }else if (StringUtils.equals(cacheInfo.getClassify(),"05")){
            rsp = getDateAggregationValue(model, cacheInfo,relation);
        }else{
            rsp = getSignAggregationValue(model, cacheInfo,relation);
        }
        //对结果进行重排序
        if (StringUtils.isNotEmpty(model.getSortWord())){
            rsp = sortResult(rsp,model.getSortWord(),model.getSortType(),model.isSortFlag());
        }
        return rsp;
    }

    private DimAggregationRsp sortResult(DimAggregationRsp rsp,String sortWord,String sortType,boolean sortFlag){
        if (rsp != null && CollectionUtils.isNotEmpty(rsp.getData())){
            List<DimAggregationRsp.R> newList;
            //直接对count进行排序
            if (sortWord.equals(CUST_NUM)){
                if (sortFlag){
                    newList = rsp.getData().stream().sorted(Comparator.comparing(DimAggregationRsp.R::getCount).reversed()).collect(Collectors.toList());
                }else{
                    newList = rsp.getData().stream().sorted(Comparator.comparing(DimAggregationRsp.R::getCount)).collect(Collectors.toList());
                }
            }else{

                Map<String,List<DimAggregationRsp.R>> map = new HashMap<>();
                //存放e.getInfo()为空相关的数据，或者过滤后的type为空数据，认为在根据某个字段排序时，没法参与排序
                List<DimAggregationRsp.R> initData = new ArrayList<>();
                rsp.getData().forEach(e->{
                    if (CollectionUtils.isEmpty(e.getInfo())){
                        initData.add(e);
                    }else{
                        List<DimAggregationRsp.Info> type = e.getInfo().stream().filter(m -> m.getField().equals(sortWord)).collect(Collectors.toList());
                        if (CollectionUtils.isEmpty(type)){
                            initData.add(e);
                        }else{
                            String value ;
                            switch (sortType){
                                case "min":
                                    value = type.get(0).getMinValue();
                                    break;
                                case "max":
                                    value = type.get(0).getMaxValue();
                                    break;
                                case "avg":
                                    value = type.get(0).getAvgValue();
                                    break;
                                case "sum":
                                    value = type.get(0).getSumValue();
                                    break;
                                default:
                                    value = "0";
                            }
                            List<DimAggregationRsp.R> listS = map.get(value);
                            if (CollectionUtils.isEmpty(listS)){
                                List<DimAggregationRsp.R> ks = new ArrayList<>();
                                ks.add(e);
                                map.put(value, ks);
                            }else{
                                listS.add(e);
                                map.put(value,listS);
                            }
                        }
                    }
                });

                List  dList = new ArrayList();
                if (sortFlag){
                    List<String> keyList = map.keySet().stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList());
                    keyList.forEach(e->{
                        dList.addAll(map.get(e));
                    });
                    dList.addAll(initData);
                }else{
                    dList.addAll(initData);
                    List<String> keyList = map.keySet().stream().sorted(Comparator.naturalOrder()).collect(Collectors.toList());
                    keyList.forEach(e->{
                        dList.addAll(map.get(e));
                    });
                }
                newList = dList;
            }
            rsp.setData(newList);
        }
        return rsp;
    }

    private DimAggregationRsp getNumberAggregationValue(DimAggregationModel model, DimInfo cacheInfo,Map<String, String> relation) throws IOException {
        // 筛选条件
        QueryBuilder queryBuilder = EsQueryBuilder.buildFilter(model.getFilterGroup());
        String column = FieldHandler.addSuffixField(model.getField(), model.getDataDate(),relation);
        SearchRequest request = getRequest(column, queryBuilder, cacheInfo, model);
        return getRangResult(ESQueryUtils.getClient().search(request, RequestOptions.DEFAULT), model,cacheInfo);
    }
    private DimAggregationRsp getDateAggregationValue(DimAggregationModel model, DimInfo cacheInfo,Map<String, String> relation) throws IOException {
        // 筛选条件
        QueryBuilder queryBuilder = EsQueryBuilder.buildFilter(model.getFilterGroup());
        String column = FieldHandler.addSuffixField(model.getField(), model.getDataDate(),relation);
        SearchRequest request = getRequest(column, queryBuilder, cacheInfo, model);
        return getRangResult(ESQueryUtils.getClient().search(request, RequestOptions.DEFAULT), model,cacheInfo);
    }
    private DimAggregationRsp getSignAggregationValue(DimAggregationModel model, DimInfo cacheInfo,Map<String, String> relation) throws IOException {
        // 筛选条件
        QueryBuilder queryBuilder = EsQueryBuilder.buildFilter(model.getFilterGroup());
        String column = FieldHandler.addSuffixField(model.getField(), model.getDataDate(),relation);
        SearchRequest request = getRequest(column, queryBuilder, cacheInfo, model);
        return getTermResult(ESQueryUtils.getClient().search(request, RequestOptions.DEFAULT), model,cacheInfo);
    }

    private SearchRequest getRequest(String dimIdColumn, QueryBuilder queryBuilder, DimInfo cacheInfo, DimAggregationModel model) {

        IndexNameInfo indexNameInfo = CacheUtils.getCacheInfo(CacheConfig.CONFIGURATION_INFO, model.getIndexId());
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(queryBuilder).trackTotalHits(true).size(0);
        //如果是数值类型
        if(StringUtils.equals("03", cacheInfo.getClassify())){
            searchSourceBuilder.aggregation(getNumberAggregation(model, dimIdColumn));
        }
        if(StringUtils.equals("05", cacheInfo.getClassify())){
            searchSourceBuilder.aggregation(getDateAggregation(model, dimIdColumn));
        }
        //如果是标志型或者集合型
        if(StringUtils.equals("02", cacheInfo.getClassify()) || StringUtils.equals("06", cacheInfo.getClassify()) || StringUtils.equals("01", cacheInfo.getClassify())){
            searchSourceBuilder.aggregation(getTermAggregation(model, dimIdColumn));
        }
        return new SearchRequest(indexNameInfo.getDataDate()).source(searchSourceBuilder);
    }

    private RangeAggregationBuilder getNumberAggregation(DimAggregationModel model, String dimId) {
        RangeAggregationBuilder rangeBuilder = AggregationBuilders.range("range_"+model.getField()).field(dimId);
        for(GroupItem item:model.getItems()){
            String low = item.getLow();
            String high = item.getHigh();
            //不为空且不含无穷
            if (!StringUtils.isEmpty(low) && !StringUtils.isEmpty(high) && !low.contains("∞") && !high.contains("∞")){
                rangeBuilder.addRange(low+"~"+high,new Double(low),new Double(high));
            }else if (!StringUtils.isEmpty(low) && StringUtils.equals(low.trim(),"-∞")) {
                rangeBuilder.addUnboundedTo(low + "~" + high, new Double(high));
            }else if (!StringUtils.isEmpty(high) && (StringUtils.equals(high.trim(),"∞") || StringUtils.equals(high.trim(),"+∞"))){
                rangeBuilder.addUnboundedFrom(low+"~+∞",new Double(low));
            }
        }
        if (CollectionUtils.isNotEmpty(model.getFields())){
            model.getFields().forEach(e -> {
                rangeBuilder.subAggregation(AggregationBuilders.max("max_"+e).field(model.getFieldsMap().get(e)));
                rangeBuilder.subAggregation(AggregationBuilders.min("min_"+e).field(model.getFieldsMap().get(e)));
                rangeBuilder.subAggregation(AggregationBuilders.avg("avg_"+e).field(model.getFieldsMap().get(e)));
                rangeBuilder.subAggregation(AggregationBuilders.sum("sum_"+e).field(model.getFieldsMap().get(e)));
            });
        }
        return rangeBuilder;
    }
    private DateRangeAggregationBuilder getDateAggregation(DimAggregationModel model, String dimId) {
        DateRangeAggregationBuilder rangeBuilder = AggregationBuilders.dateRange("range_"+model.getField()).field(dimId);
        for(GroupItem item:model.getItems()){
            String low = item.getLow();
            String high = item.getHigh();
            //不为空且不含无穷
            if (!StringUtils.isEmpty(low) && !StringUtils.isEmpty(high) && !low.contains("∞") && !high.contains("∞")){
                low = LocalDate.parse(item.getLow(), DateTimeFormatter.BASIC_ISO_DATE).format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
                high = LocalDate.parse(item.getHigh(), DateTimeFormatter.BASIC_ISO_DATE).format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
                rangeBuilder.addRange(low + "/" + high,low,high);
            }else if (!StringUtils.isEmpty(low) && StringUtils.equals(low.trim(),"-∞")) {
                high = LocalDate.parse(item.getHigh(), DateTimeFormatter.BASIC_ISO_DATE).format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
                rangeBuilder.addUnboundedTo(low + "/" + high, high);
            }else if (!StringUtils.isEmpty(high) && (StringUtils.equals(high.trim(),"∞") || StringUtils.equals(high.trim(),"+∞"))){
                low = LocalDate.parse(item.getLow(), DateTimeFormatter.BASIC_ISO_DATE).format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
                rangeBuilder.addUnboundedFrom(low+"/+∞",low);
            }
        }
        if (CollectionUtils.isNotEmpty(model.getFields())){
            model.getFields().forEach(e -> {
                rangeBuilder.subAggregation(AggregationBuilders.max("max_"+e).field(model.getFieldsMap().get(e)));
                rangeBuilder.subAggregation(AggregationBuilders.min("min_"+e).field(model.getFieldsMap().get(e)));
                rangeBuilder.subAggregation(AggregationBuilders.avg("avg_"+e).field(model.getFieldsMap().get(e)));
                rangeBuilder.subAggregation(AggregationBuilders.sum("sum_"+e).field(model.getFieldsMap().get(e)));
            });
        }
        return rangeBuilder;
    }

    private TermsAggregationBuilder getTermAggregation(DimAggregationModel model, String dimId) {
        TermsAggregationBuilder termBuilder = AggregationBuilders.terms("group_"+model.getField()).field(dimId).order(BucketOrder.key(true)).size(Integer.MAX_VALUE);
        if (CollectionUtils.isNotEmpty(model.getFields())){
            model.getFields().forEach(e -> {
                termBuilder.subAggregation(AggregationBuilders.max("max_"+e).field(model.getFieldsMap().get(e)));
                termBuilder.subAggregation(AggregationBuilders.min("min_"+e).field(model.getFieldsMap().get(e)));
                termBuilder.subAggregation(AggregationBuilders.avg("avg_"+e).field(model.getFieldsMap().get(e)));
                termBuilder.subAggregation(AggregationBuilders.sum("sum_"+e).field(model.getFieldsMap().get(e)));
            });
        }
        return termBuilder;
    }

    private DimAggregationRsp getRangResult(SearchResponse response, DimAggregationModel model,DimInfo cacheInfo){
        Range terms = response.getAggregations().get("range_"+model.getField());
        List<DimAggregationRsp.R> result = new ArrayList<>();
        for (Range.Bucket entry : terms.getBuckets()) {
            List<DimAggregationRsp.Info> data = new ArrayList<>();
            if (CollectionUtils.isNotEmpty(model.getFields())){
                model.getFields().forEach(e -> {
                    Max max = entry.getAggregations().get("max_" + e);
                    Min min = entry.getAggregations().get("min_" + e);
                    Avg avg = entry.getAggregations().get("avg_" + e);
                    Sum sum = entry.getAggregations().get("sum_" + e);
                    data.add(DimAggregationRsp.Info.of().setField(e).setMaxValue(realValue(max.getValueAsString())).setMinValue(realValue(min.getValueAsString())).setSumValue(realValue(sum.getValueAsString())).setAvgValue(realValue(avg.getValueAsString())));
                });
            }
            result.add(DimAggregationRsp.R.of().setTitle(entry.getKeyAsString()).setCount(entry.getDocCount()).setInfo(data));
        }
        sort(cacheInfo.getClassify(),result);
        return DimAggregationRsp.of().setCode(0).setData(result).setFieldName(cacheInfo.getDimName());
    }

    private DimAggregationRsp getTermResult(SearchResponse response, DimAggregationModel model,DimInfo cacheInfo){
        Terms terms = response.getAggregations().get("group_"+model.getField());
        List<DimAggregationRsp.R> result = new ArrayList<>();
        for (Terms.Bucket entry : terms.getBuckets()) {
            List<DimAggregationRsp.Info> data = new ArrayList<>();
            if (CollectionUtils.isNotEmpty(model.getFields())){
                model.getFields().forEach(e -> {
                    Max max = entry.getAggregations().get("max_" + e);
                    Min min = entry.getAggregations().get("min_" + e);
                    Avg avg = entry.getAggregations().get("avg_" + e);
                    Sum sum = entry.getAggregations().get("sum_" + e);
                    data.add(DimAggregationRsp.Info.of().setField(e).setMaxValue(realValue(max.getValueAsString())).setMinValue(realValue(min.getValueAsString())).setSumValue(realValue(sum.getValueAsString())).setAvgValue(realValue(avg.getValueAsString())));
                });
            }
            String title = Optional
                    .ofNullable((DimItemInfo) CacheUtils.getCacheInfo(CacheConfig.DIM_ITEM_INFO,
                            String.format(BaseParse.GROUP_FORMAT, model.getField(), entry.getKeyAsString())))
                    .map(DimItemInfo::getDimItemName)
                    .orElse(entry.getKeyAsString());
            if ("06".equals(cacheInfo.getClassify())){
                title = joinStr(title,entry.getKeyAsString());
            }
            result.add(DimAggregationRsp.R.of().setTitle(title).setCount(entry.getDocCount()).setInfo(data));
        }
        return DimAggregationRsp.of().setCode(0).setData(result).setFieldName(cacheInfo.getDimName());
    }

    /**
     * 计算真实value值
     * @param valueStr
     * @return
     */
    private String realValue(String valueStr){
        valueStr = Optional.ofNullable(valueStr).orElse("0.00");
        if (pattern.matcher(valueStr).matches()){
            return String.valueOf(new BigDecimal(valueStr).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue());
        }else if(valueStr.contains("Infinity")){
            return "0.00";
        }else{
            return new BigDecimal(valueStr).setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString();
        }
    }

    public String joinStr(String key,String value){
        StringBuffer buffer = new StringBuffer();
        buffer.append(key).append("(").append(value).append(")");
        return buffer.toString();
    }



    private void sort(String classify, List<DimAggregationRsp.R> data) {
        if(StringUtils.equals(classify,"03")){
            data.sort((o1, o2) -> {
                Double v1 = getValue(o1.getTitle());
                Double v2 = getValue(o2.getTitle());
                return v1.compareTo(v2);
            });
        }
        if(StringUtils.equals(classify,"05")){
            data.sort((o1, o2) -> {
                String title1 = o1.getTitle();
                String title2 = o2.getTitle();
                if(StringUtils.contains(title1,"-∞")||StringUtils.contains(title2,"+∞")){
                    return -1;
                }
                if(StringUtils.contains(title1,"+∞")||StringUtils.contains(title2,"-∞")){
                    return 1;
                }
                return StringUtils.compare(title1, title2);
            });
        }
    }

    private Double getValue(String D1) {
        String[] array = D1.split("~");
        if(D1.indexOf("-")==0){
            if(StringUtils.equals("-∞",array[0])){
                return Double.MIN_VALUE;
            }
            return Double.parseDouble(array[0]);
        }else{
            if(StringUtils.equals("+∞",array[0]) || StringUtils.equals("∞",array[0])){
                return Double.MAX_VALUE;
            }
            return Double.parseDouble(array[0]);
        }
    }


}
